﻿#include <QMouseEvent>
#include <QPainter>
#include <QElapsedTimer>
#include "JZGLView.h"

//JZGLView
JZGLView::JZGLView(QWidget* parent) 
    : QOpenGLWidget(parent) 
{
    setFocusPolicy(Qt::ClickFocus);
    m_camera.setWidget(this);
    m_mouseDown = false;
    m_scene = nullptr;
    m_highDpi = 1;
    m_worldModel.setToIdentity();
}

JZGLView::~JZGLView() 
{
    if(m_scene)
        m_scene->removeView(this);

    makeCurrent();
    m_meshBuffer.destroy();
    m_meshIndexBuffer.destroy();
    m_lineBuffer.destroy();
    m_lineIndexBuffer.destroy();
    doneCurrent();
}

void JZGLView::mousePressEvent(QMouseEvent* e)
{
    if (e->button() == Qt::LeftButton)
    {
        m_mouseDownPos = e->pos();
        m_mouseDown = true;
    }
}

void JZGLView::mouseMoveEvent(QMouseEvent* e)
{
}

void JZGLView::mouseReleaseEvent(QMouseEvent* e)
{
    m_mouseDownPos = QPoint();
    m_mouseDown = false;
}

void JZGLView::keyPressEvent(QKeyEvent* event)
{
    int key = event->key();
    if (key == Qt::Key_Up || key == Qt::Key_Down
        || key == Qt::Key_Left || key == Qt::Key_Right)
    {
        float x_add = 0, y_add = 0;
        if (key == Qt::Key_Up)
            x_add = -10;
        else if (key == Qt::Key_Down)
            x_add = 10;
        else if (key == Qt::Key_Left)
            y_add = -10;
        else if (key == Qt::Key_Right)
            y_add = 10;
    }
}

void JZGLView::keyReleaseEvent(QKeyEvent* event)
{

}

JZGLCamera* JZGLView::camera()
{
    return &m_camera;
}

void JZGLView::setScene(JZGLScene* scene)
{
    m_scene = scene;
    m_scene->addView(this);
    updateItemBuffer();
}

JZGLScene* JZGLView::scene()
{
    return m_scene;
}

float JZGLView::getDepth(int x, int y)
{
    x = x * m_highDpi;
    y = (height() - y) * m_highDpi;
    float depth = 1.0;

    makeCurrent();
    glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth);
    doneCurrent();

    return depth;
}

QPoint JZGLView::worldToPixel(QVector3D world)
{
    return m_camera.project(world.x(), world.y(), world.z());
}

QVector3D JZGLView::pixelToWorld(QPoint pt)
{
    int x = pt.x();
    int y = pt.y();
    return m_camera.unProject(x, y, getDepth(x, y));
}

void JZGLView::initializeGL()
{
    initializeOpenGLFunctions();
    QOpenGLContext* context = this->context();
    QSurfaceFormat format = context->format();
    format.setDepthBufferSize(32);
    context->setFormat(format);

    glClearColor(0, 0, 0, 1);
    initShaders();

    m_lineIndexBuffer = QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
    m_meshIndexBuffer = QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
    
    m_meshBuffer.create();
    m_meshIndexBuffer.create();
    
    m_lineBuffer.create();
    m_lineIndexBuffer.create();
}

void JZGLView::resizeGL(int w, int h)
{    
    m_highDpi = devicePixelRatio();
    setViewport(0, 0, w, h);
}

void JZGLView::setViewport(int x, int y, int w, int h)
{
    glViewport(x, y, w, h);
    m_camera.setViewport(w, h);
}

void JZGLView::paintGL()
{
    // Clear color and depth buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 启用深度测试
    glEnable(GL_DEPTH_TEST);
    // 设置深度测试函数（一般使用默认的GL_LESS即可）
    glDepthFunc(GL_LESS);

    m_program.bind();

    QMatrix4x4 matrix = m_camera.getProjectionMatrix() * m_camera.getViewMatrix() * m_worldModel;
    m_program.setUniformValue("mvp_matrix", matrix);

    draw3DItems();
    m_program.release();
    glFinish();

    draw2DItems();
}

void JZGLView::initShaders()
{
    QString vertexShader = R"(
        uniform mat4 mvp_matrix;
        attribute vec4 a_position;
        in vec3 a_color;    //颜色属性
        out vec3 v_color;   //传递顶点的颜色给片段着色器
        
        void main()
        {
            // Calculate vertex position in screen space
            gl_Position = mvp_matrix * a_position;
            v_color = a_color; 
        }
    )";

    QString fragmentShader = R"(
        in vec3 v_color; // 接收从顶点着色器传递过来的颜色属性
        void main()
        {
            gl_FragColor = vec4(v_color, 1.0);
        }    
    )";

    // Compile vertex shader
    if (!m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShader))
        close();

    // Compile fragment shader
    if (!m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShader))
        close();

    // Link shader pipeline
    if (!m_program.link())
        close();

    // Bind shader pipeline for use
    if (!m_program.bind())
        close();
}

void JZGLView::updateItemBuffer()
{
    if (!m_scene || !QOpenGLFunctions::d_ptr)
    {
        update();
        return;
    }

    QVector<VertexData> meshVertex;
    QVector<VertexData> lineVertex;
    QVector<int> meshIndex;
    QVector<int> lineIndex;
    
    int mesh_start = 0;
    int line_start = 0;
    auto it = m_scene->m_itemData.begin();
    while (it != m_scene->m_itemData.end())
    {
        auto& d = it.value();

        meshVertex.append(d.meshVertex);
        for (int i = 0; i < d.meshIndex.size(); i++)
            meshIndex.push_back(mesh_start + d.meshIndex[i]);
        mesh_start += d.meshIndex.size();

        lineVertex.append(d.lineVertex);
        for (int i = 0; i < d.lineIndex.size(); i++)
            lineIndex.push_back(line_start + d.lineIndex[i]);
        line_start += d.lineIndex.size();
        it++;
    }

    QVector<float> mesh_vertex = vertexToFloat(meshVertex);
    QVector<float> line_vertex = vertexToFloat(lineVertex);

    m_meshBuffer.bind();
    m_meshBuffer.allocate(mesh_vertex.data(), mesh_vertex.size() * (int)sizeof(GLfloat));
    m_meshBuffer.release();

    m_meshIndexBuffer.bind();
    m_meshIndexBuffer.allocate(meshIndex.data(), meshIndex.size() * (int)sizeof(GLuint));
    m_meshIndexBuffer.release();

    m_lineBuffer.bind();
    m_lineBuffer.allocate(line_vertex.data(), line_vertex.size() * (int)sizeof(GLfloat));
    m_lineBuffer.release();

    m_lineIndexBuffer.bind();
    m_lineIndexBuffer.allocate(lineIndex.data(), lineIndex.size() * (int)sizeof(GLuint));
    m_lineIndexBuffer.release();

    update();
}

void JZGLView::drawMeshItems()
{
    // Offset for position
    quintptr offset = 0;
    int vertex_size = 6 * sizeof(float);

    // Tell OpenGL which VBOs to use
    m_meshBuffer.bind();
    m_meshIndexBuffer.bind();

    int index_size = m_meshIndexBuffer.size() / sizeof(GLuint);

    // Tell OpenGL programmable pipeline how to locate vertex position data
    int vertexLocation = m_program.attributeLocation("a_position");
    m_program.enableAttributeArray(vertexLocation);
    m_program.setAttributeBuffer(vertexLocation, GL_FLOAT, offset, 3, vertex_size);
    offset += 3 * sizeof(float);

    int colorLocation = m_program.attributeLocation("a_color");
    m_program.enableAttributeArray(colorLocation);
    m_program.setAttributeBuffer(colorLocation, GL_FLOAT, offset, 3, vertex_size);

    // Draw cube geometry using indices from VBO 1
    glDrawElements(GL_TRIANGLES, index_size, GL_UNSIGNED_INT, nullptr);

    m_meshBuffer.release();
    m_meshIndexBuffer.release();
}

void JZGLView::drawLineItems()
{
    quintptr offset = 0;
    int vertex_size = 6 * sizeof(float);

    // Draw line
    m_lineBuffer.bind();
    m_lineIndexBuffer.bind();

    int index_size = m_lineIndexBuffer.size() / sizeof(GLuint);

    int vertexLocation = m_program.attributeLocation("a_position");
    m_program.enableAttributeArray(vertexLocation);
    m_program.setAttributeBuffer(vertexLocation, GL_FLOAT, offset, 3, vertex_size);
    offset += 3 * sizeof(float);

    int colorLocation = m_program.attributeLocation("a_color");
    m_program.enableAttributeArray(colorLocation);
    m_program.setAttributeBuffer(colorLocation, GL_FLOAT, offset, 3, vertex_size);

    // Draw cube geometry using indices from VBO 1
    glDrawElements(GL_LINES, index_size, GL_UNSIGNED_INT, nullptr);

    m_lineBuffer.release();
    m_lineIndexBuffer.release();
}

void JZGLView::draw2DItems()
{
    QPainter painter(this);
    QMatrix4x4 model;
    model.setToIdentity();
    /*
    for (int i = 0; i < m_items.size(); i++)
    {
        auto item = m_items[i];
        item->paintWorld(&painter,model);
    }
    */
}

QVector<float> JZGLView::vertexToFloat(const QVector<VertexData>& points)
{
    QVector<float> list;
    for (int i = 0; i < points.size(); i++)
    {
        auto& pt = points[i];
        list.push_back(pt.pos.x());
        list.push_back(pt.pos.y());
        list.push_back(pt.pos.z());
        list.push_back(pt.color.redF());
        list.push_back(pt.color.greenF());
        list.push_back(pt.color.blueF());
    }
    return list;
}

void JZGLView::draw3DItems()
{
    drawMeshItems();
    drawLineItems();
}